/*
 * jopengl_GLWin.cpp
 *
 *  Created on: 2013-10-23
 *      Author: pan.jinghua
 */

#include <windows.h>
#include <stdio.h>

#include "jopengl_GLWin.h"

typedef struct {
    JNIEnv *env;
    jobject self;
    char name[20];
    bool running;
    HINSTANCE hInst;
    HWND hWnd;
    WNDCLASS wCls;
    HDC hDC;
    HGLRC hRC;
} WndInfo;

WndInfo* newWndInfo(JNIEnv *env, jobject self) {
    WndInfo* wi = (WndInfo*)malloc(sizeof(WndInfo));
    wi->env = env;
    wi->self = env->NewGlobalRef(self);
    strcpy(wi->name, "JOpenGL!");
    itoa((int)wi, &wi->name[8], 16);

    jclass clz = env->GetObjectClass(self);
    jfieldID fWndInfo = env->GetFieldID(clz, "wndInfoAddr", "J");
    env->SetLongField(self, fWndInfo, (jlong)wi);
    env->DeleteLocalRef(clz);
    return wi;
}

WndInfo* getWndInfo(JNIEnv *env, jobject self) {
    jclass clz = env->GetObjectClass(self);
    jfieldID fWndInfo = env->GetFieldID(clz, "wndInfoAddr", "J");
    WndInfo* wi = (WndInfo*)env->GetLongField(self, fWndInfo);
    env->DeleteLocalRef(clz);
    return wi;
}

void releaseWndInfo(WndInfo* wi) {
    wi->env->DeleteGlobalRef(wi->self);
    free(wi);
}

LRESULT CALLBACK wproc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

void appendMenu(HMENU parent, jobjectArray arr, JNIEnv *env, jclass clzInteger, jmethodID mIntValue) {
    int len = env->GetArrayLength(arr);
    for (int i = 0; i < len; i += 2) {
        jstring name = (jstring)env->GetObjectArrayElement(arr, i);
        jobject value = env->GetObjectArrayElement(arr, i + 1);
        LPWSTR cv = (LPWSTR)env->GetStringChars(name, 0);
        if (env->IsInstanceOf(value, clzInteger)) {
            int id = env->CallIntMethod(value, mIntValue);
            AppendMenuW(parent, MF_STRING, id, cv);
        } else {
            HMENU sub = CreatePopupMenu();
            AppendMenuW(parent, MF_POPUP, (UINT_PTR)sub, cv);
            appendMenu(sub, (jobjectArray)value, env, clzInteger, mIntValue);
        }
        env->ReleaseStringChars(name, (jchar*)cv);
        env->DeleteLocalRef(name);
        env->DeleteLocalRef(value);
    }
}

HMENU createMenu(JNIEnv *env, jobject menu) {
    if (!menu) return NULL;
    jclass clz = env->GetObjectClass(menu);
    jmethodID mToArray = env->GetMethodID(clz, "toArray", "()[Ljava/lang/Object;");
    jobjectArray m =  (jobjectArray)env->CallObjectMethod(menu, mToArray);
    env->DeleteLocalRef(clz);
    clz = env->FindClass("java/lang/Integer");
    jmethodID mIntValue = env->GetMethodID(clz, "intValue", "()I");
    HMENU hMenu = CreateMenu();
    appendMenu(hMenu, m, env, clz, mIntValue);
    env->DeleteLocalRef(clz);
    return hMenu;
}

jboolean JNICALL Java_jopengl_GLWin_create(JNIEnv *env, jobject self, jint width, jint height, jint left, jint top, jstring caption, jobject menu) {
    setvbuf(stdout, NULL, _IONBF, 0);
    WndInfo* wi = newWndInfo(env, self);

    wi->wCls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
    wi->wCls.hInstance = wi->hInst = GetModuleHandle(NULL);
    wi->wCls.lpfnWndProc = wproc;
    wi->wCls.lpszClassName = wi->name;
    wi->wCls.hIcon = LoadIcon(NULL, IDI_WINLOGO);
    wi->wCls.hCursor = LoadCursor(NULL, IDC_ARROW);
    if (!RegisterClass(&wi->wCls)) {
        releaseWndInfo(wi);
        return false;
    }
    bool fullscreen = false;
    const char* str = env->GetStringUTFChars(caption, 0);
    wi->hWnd = CreateWindowEx(
            WS_EX_APPWINDOW | WS_EX_WINDOWEDGE,
            wi->name,
            str,
            fullscreen ? WS_POPUP : WS_OVERLAPPEDWINDOW,
            left, top, width, height,
            NULL,
            createMenu(env, menu),
            wi->hInst,
            NULL
    );
//    printf("className: %s, hWnd: %d, caption: %s\n", wi->si->name, (int)wi->hWnd, str);
    env->ReleaseStringUTFChars(caption, str);

    static PIXELFORMATDESCRIPTOR pfd = {
            sizeof(PIXELFORMATDESCRIPTOR),  // size
            1,  //version
            PFD_DRAW_TO_WINDOW |
            PFD_SUPPORT_OPENGL |
            PFD_DOUBLEBUFFER,
            PFD_TYPE_RGBA,
            32,
            0, 0, 0, 0, 0, 0,
            0,
            0,
            0,
            0, 0, 0, 0,
            16,
            0,
            0,
            PFD_MAIN_PLANE,
            0,
            0, 0, 0
    };

    wi->hDC = GetDC(wi->hWnd);
//    printf("w: %d\n", GetDeviceCaps(wi->hDC, HORZRES));
//    printf("h: %d\n", GetDeviceCaps(wi->hDC, VERTRES));
    int pixelFormat = ChoosePixelFormat(wi->hDC, &pfd);
    SetPixelFormat(wi->hDC, pixelFormat, &pfd);
    wi->hRC = wglCreateContext(wi->hDC);
    if (!wi->hRC) return false;
    wglMakeCurrent(wi->hDC, wi->hRC);
    ShowWindow(wi->hWnd, SW_SHOW);
    SetForegroundWindow(wi->hWnd);
    SetFocus(wi->hWnd);

    return true;
}

void JNICALL Java_jopengl_GLWin_close(JNIEnv *env, jobject self) {
    WndInfo* wi = getWndInfo(env, self);
    wi->running = false;
}

void JNICALL Java_jopengl_GLWin_loop(JNIEnv *env, jobject self) {
    WndInfo* wi = getWndInfo(env, self);
    jclass clz = env->GetObjectClass(self);
    jmethodID mRender = env->GetMethodID(clz, "render", "()V");
    MSG msg;
    wi->running = true;
    while (wi->running) {
        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        } else {
            env->CallVoidMethod(self, mRender);
            SwapBuffers(wi->hDC);
        }
    }
    env->DeleteLocalRef(clz);
    wglMakeCurrent(NULL, NULL);
    wglDeleteContext(wi->hRC);
    wi->hRC = NULL;
    ReleaseDC(wi->hWnd, wi->hDC);
    wi->hDC = NULL;
    DestroyWindow(wi->hWnd);
    wi->hWnd = NULL;
    UnregisterClass(wi->name, wi->hInst);

    releaseWndInfo(wi);
}

jint JNICALL Java_jopengl_GLWin_defproc(JNIEnv *env, jobject self, jint hwnd, jint msg, jint wParam, jint lParam) {
    return DefWindowProc((HWND)hwnd, msg, wParam, lParam);
}

LRESULT CALLBACK wproc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    char name[20];
    GetClassName(hwnd, name, sizeof(name));
    WndInfo* wi = (WndInfo*)strtol(&name[8], NULL, 16);
    JNIEnv *env = wi->env;
    jobject self = wi->self;
    jclass clz = env->GetObjectClass(self);
    jmethodID mProc = env->GetMethodID(clz, "proc", "(IIII)I");
//    printf("wproc start %d, %d, %d, %d, %d, %d, %d\n", (int)self, (int)clz, (int)mProc, (int)hwnd, msg, wParam, (int)lParam);
    LRESULT result;
    result = env->CallIntMethod(self, mProc, hwnd, msg, wParam, lParam);
//    result = DefWindowProc(hwnd, msg, wParam, lParam);
//    printf("wproc result %d\n", (int)result);
    env->DeleteLocalRef(clz);
    return result;
}

void JNICALL Java_jopengl_GLWin_set(JNIEnv *env, jobject self, jint left, jint top, jint width, jint height, jboolean border, jboolean topMost) {
    WndInfo* wi = getWndInfo(env, self);
    SetWindowLong(wi->hWnd, GWL_STYLE, border ? WS_OVERLAPPEDWINDOW : WS_POPUP);
    SetWindowPos(wi->hWnd, topMost ? HWND_TOPMOST : HWND_NOTOPMOST, left, top, width, height, SWP_SHOWWINDOW);
}

void JNICALL Java_jopengl_GLWin_fullscreen(JNIEnv *env, jobject self, jboolean topMost) {
    Java_jopengl_GLWin_set(env, self, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN), false, topMost);
}

